/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年5月23日
 */
package com.massyframework.beanskin.util.version;

import java.util.NoSuchElementException;
import java.util.StringTokenizer;

/**
 * {@link VersionRange},描述一组版本区间。
 * 
 * <p>
 * 摘抄来自osgi的VersionRange实现.
 */
public final class VersionRange {

	/**
     * 开放符号，表示左边取值点包含在范围内，
     */
    public static final char	LEFT_OPEN				= '(';
    
    /**
     * 关闭符号，表示左边取值点不在范围内。
     */
    public static final char	LEFT_CLOSED				= '[';
    
    /**
     * 开放符号，表示右边取值点包含在范围内。
     */
    public static final char	RIGHT_OPEN				= ')';
    
    /**
     * 关闭符号，表示右边取值点不在范围内。
     */
    public static final char	RIGHT_CLOSED			= ']';
    
    private final boolean		leftClosed;
    private final Version		left;
    private final Version		right;
    private final boolean		rightClosed;
    private final boolean		empty;
    
    private transient String	versionRangeString /* 缺省值: null */;
    private transient int		hash /* 缺省值:  0 */;

    private static final String	LEFT_OPEN_DELIMITER		= "(";
    private static final String	LEFT_CLOSED_DELIMITER	= "[";
    private static final String	LEFT_DELIMITERS			= LEFT_CLOSED_DELIMITER + LEFT_OPEN_DELIMITER;
    private static final String	RIGHT_OPEN_DELIMITER	= ")";
    private static final String	RIGHT_CLOSED_DELIMITER	= "]";
    private static final String	RIGHT_DELIMITERS		= RIGHT_OPEN_DELIMITER + RIGHT_CLOSED_DELIMITER;
    private static final String	ENDPOINT_DELIMITER		= ",";

    public static final VersionRange EMPTY = new VersionRange("0.0.0");

    /**
     * 根据指定的区间创建一个{@link VersionRange}
     *      *
     * @param leftType {@link char},左边开闭类型，必须为{@link #LEFT_CLOSED}或者 {@link #LEFT_OPEN}
     *        .
     * @param leftEndpoint {@link String},左边端点.
     * @param rightEndpoint {@link String}, 右边端点，可以为null.
     * @param rightType {@link char}, 右边开闭类型，必须为{@link #RIGHT_CLOSED}或者  {@link #RIGHT_OPEN}.
     * @throws IllegalArgumentException 参数无效时抛出的例外.
     */
    public VersionRange(char leftType, Version leftEndpoint, Version rightEndpoint, char rightType) {
        if ((leftType != LEFT_CLOSED) && (leftType != LEFT_OPEN)) {
            throw new IllegalArgumentException("invalid leftType \"" + leftType + "\"");
        }
        if ((rightType != RIGHT_OPEN) && (rightType != RIGHT_CLOSED)) {
            throw new IllegalArgumentException("invalid rightType \"" + rightType + "\"");
        }
        if (leftEndpoint == null) {
            throw new IllegalArgumentException("null leftEndpoint argument");
        }
        leftClosed = leftType == LEFT_CLOSED;
        rightClosed = rightType == RIGHT_CLOSED;
        left = leftEndpoint;
        right = rightEndpoint;
        empty = isEmpty0();
    }

    /**
     * 根据特定的字符串创建一个版本区域
     *      *
     * <p>
     * 版本范围字符串应该符合以下语法:
     *
     * <pre>
     * range ::= interval | atleast
     * interval ::= ( '[' | '(' ) left ',' right ( ']' | ')' )
     * left ::= version
     * right ::= version
     * atleast ::= version
     * </pre>
     *
     * @param range {@link String}, 表达一个版本的字符串，不应该包含空格.
     * @throws IllegalArgumentException {@code range}格式不正确将抛出例外
     */
    public VersionRange(String range) {
        boolean closedLeft;
        boolean closedRight;
        Version endpointLeft;
        Version endpointRight;

        try {
            StringTokenizer st = new StringTokenizer(range, LEFT_DELIMITERS, true);
            String token = st.nextToken().trim(); // whitespace or left delim
            if (token.length() == 0) { // leading whitespace
                token = st.nextToken(); // left delim
            }
            closedLeft = LEFT_CLOSED_DELIMITER.equals(token);
            if (!closedLeft && !LEFT_OPEN_DELIMITER.equals(token)) {
                // first token is not a delimiter, so it must be "atleast"
                if (st.hasMoreTokens()) { // there must be no more tokens
                    throw new IllegalArgumentException("invalid range \"" + range + "\": invalid format");
                }
                leftClosed = true;
                rightClosed = false;
                left = parseVersion(token, range);
                right = null;
                empty = false;
                return;
            }
            String version = st.nextToken(ENDPOINT_DELIMITER);
            endpointLeft = parseVersion(version, range);
            token = st.nextToken(); // consume comma
            version = st.nextToken(RIGHT_DELIMITERS);
            token = st.nextToken(); // right delim
            closedRight = RIGHT_CLOSED_DELIMITER.equals(token);
            if (!closedRight && !RIGHT_OPEN_DELIMITER.equals(token)) {
                throw new IllegalArgumentException("invalid range \"" + range + "\": invalid format");
            }
            endpointRight = parseVersion(version, range);

            if (st.hasMoreTokens()) { // any more tokens have to be whitespace
                token = st.nextToken("").trim();
                if (token.length() != 0) { // trailing whitespace
                    throw new IllegalArgumentException("invalid range \"" + range + "\": invalid format");
                }
            }
        } catch (NoSuchElementException e) {
            IllegalArgumentException iae = new IllegalArgumentException("invalid range \"" + range + "\": invalid format");
            iae.initCause(e);
            throw iae;
        }

        leftClosed = closedLeft;
        rightClosed = closedRight;
        left = endpointLeft;
        right = endpointRight;
        empty = isEmpty0();
    }

    /**
     * 解析{@code version}为{@link Version}实例
     * 
     * @param version {@link String}, 版本字符串
     * @param range {@link String}, 解析发生例外时所需的范围信息
     * @return Version {@link Version}
     */
    private static Version parseVersion(String version, String range) {
        try {
            return Version.valueOf(version);
        } catch (IllegalArgumentException e) {
            IllegalArgumentException iae = new IllegalArgumentException("invalid range \"" + range + "\": " + e.getMessage());
            iae.initCause(e);
            throw iae;
        }
    }

    /**
     * 左端点
     *
     * @return {@link Version}
     */
    public Version getLeft() {
        return left;
    }

    /**
     * 右端点
     *
     * @return {@link Version},返回{@link null}表示无限
     */
    public Version getRight() {
        return right;
    }

    /**
     * 左端点开闭类型
     *
     * @return {@link char}, 开放端点返回{@link #LEFT_OPEN}, 关闭端点返回{@link #LEFT_CLOSED}
     */
    public char getLeftType() {
        return leftClosed ? LEFT_CLOSED : LEFT_OPEN;
    }

    /**
     * 右端点开闭类型
     *
     * @return {@link char}, 开放端点返回{@link #RIGHT_OPEN}. 关闭端点返回{@link #RIGHT_CLOSED}.
     */
    public char getRightType() {
        return rightClosed ? RIGHT_CLOSED : RIGHT_OPEN;
    }

    /**
     * 判断{@code version}是否在本版本范围内。
     *
     * @param version {@link Version}, 被判断版本
     * @return {@link boolean},返回{@code true}表示在范围内，否则返回{@code false}.
     */
    public boolean includes(Version version) {
    	if (version == null) {
    		return false;
    	}
    	
        if (empty) {
            return false;
        }
        if (left.compareTo(version) >= (leftClosed ? 1 : 0)) {
            return false;
        }
        if (right == null) {
            return true;
        }
        return right.compareTo(version) >= (rightClosed ? 0 : 1);
    }

    /**
     * 返回和{@code ranges}的范围交集
     *
     * @param ranges {@link VersionRange}数组
     * @return {@link VersionRange}, 交集范围，如果{@code ranges}为null,则返回本版本范围
     */
    public VersionRange intersection(VersionRange... ranges) {
        if ((ranges == null) || (ranges.length == 0)) {
            return this;
        }
        // prime with data from this version range
        boolean closedLeft = leftClosed;
        boolean closedRight = rightClosed;
        Version endpointLeft = left;
        Version endpointRight = right;

        for (VersionRange range : ranges) {
            int comparison = endpointLeft.compareTo(range.left);
            if (comparison == 0) {
                closedLeft = closedLeft && range.leftClosed;
            } else {
                if (comparison < 0) { // move endpointLeft to the right
                    endpointLeft = range.left;
                    closedLeft = range.leftClosed;
                }
            }
            if (range.right != null) {
                if (endpointRight == null) {
                    endpointRight = range.right;
                    closedRight = range.rightClosed;
                } else {
                    comparison = endpointRight.compareTo(range.right);
                    if (comparison == 0) {
                        closedRight = closedRight && range.rightClosed;
                    } else {
                        if (comparison > 0) { // move endpointRight to the left
                            endpointRight = range.right;
                            closedRight = range.rightClosed;
                        }
                    }
                }
            }
        }

        return new VersionRange(closedLeft ? LEFT_CLOSED : LEFT_OPEN, endpointLeft, endpointRight, closedRight ? RIGHT_CLOSED : RIGHT_OPEN);
    }

    /**
     * 判断版本范围是否为empty
     *
     * @return {@link Boolean}， 返回 {@code true}表示为空范围，否则返回{@link false}. 
     */
    public boolean isEmpty() {
        return empty;
    }

    /**
     * 内部的isEmpty
     *
     * @return {@link Boolean}， 返回 {@code true}表示为空范围，否则返回{@link false}. 
     */
    private boolean isEmpty0() {
        if (right == null) { // infinity
            return false;
        }
        int comparison = left.compareTo(right);
        if (comparison == 0) { // endpoints equal
            return !leftClosed || !rightClosed;
        }
        return comparison > 0; // true if left > right
    }

    /**
     * 判断范围是否仅只包含一个版本
     *
     * @return  {@link Boolean}， 返回 {@code true}表示为仅包含一个版本，否则返回{@link false}. 
     */
    public boolean isExact() {
        if (empty || (right == null)) {
            return false;
        }
        if (leftClosed) {
            if (rightClosed) {
                // [l,r]: exact if l == r
                return left.equals(right);
            } else {
                // [l,r): exact if l++ >= r
                Version adjacent1 = new Version(left.getMajor(), left.getMinor(), left.getMicro(), left.getQualifier() + "-");
                return adjacent1.compareTo(right) >= 0;
            }
        } else {
            if (rightClosed) {
                // (l,r] is equivalent to [l++,r]: exact if l++ == r
                Version adjacent1 = new Version(left.getMajor(), left.getMinor(), left.getMicro(), left.getQualifier() + "-");
                return adjacent1.equals(right);
            } else {
                // (l,r) is equivalent to [l++,r): exact if (l++)++ >=r
                Version adjacent2 = new Version(left.getMajor(), left.getMinor(), left.getMicro(), left.getQualifier() + "--");
                return adjacent2.compareTo(right) >= 0;
            }
        }
    }

    /**
     * 版本范围的字符串表达形式
     *
     * @return {@link String}
     */
    @Override
    public String toString() {
        String s = versionRangeString;
        if (s != null) {
            return s;
        }
        String leftVersion = left.toString();
        if (right == null) {
            StringBuffer result = new StringBuffer(leftVersion.length() + 1);
            result.append(left.toString0());
            return versionRangeString = result.toString();
        }
        String rightVerion = right.toString();
        StringBuffer result = new StringBuffer(leftVersion.length() + rightVerion.length() + 5);
        result.append(leftClosed ? LEFT_CLOSED : LEFT_OPEN);
        result.append(left.toString0());
        result.append(ENDPOINT_DELIMITER);
        result.append(right.toString0());
        result.append(rightClosed ? RIGHT_CLOSED : RIGHT_OPEN);
        return versionRangeString = result.toString();
    }

    /**
     * hash code
     *
     * @return {@link int}
     */
    @Override
    public int hashCode() {
        int h = hash;
        if (h != 0) {
            return h;
        }
        if (empty) {
            return hash = 31;
        }
        h = 31 + (leftClosed ? 7 : 5);
        h = 31 * h + left.hashCode();
        if (right != null) {
            h = 31 * h + right.hashCode();
            h = 31 * h + (rightClosed ? 7 : 5);
        }
        return hash = h;
    }

    /**
     * 比较是否等于{@code object}
     *
     * <p>
     * 一个版本范围等于另一个版本范围，需要：
     * 左右端点和开闭类型相同，或者{@link #isEmpty()}都为{@code true}.
     *
     * @param object {@code VersionRange}
     * @return {@link boolean}, 返回{@code true}表示相同，否则返回{@code false}.
     */
    @Override
    public boolean equals(Object object) {
        if (object == this) { // quicktest
            return true;
        }
        if (!(object instanceof VersionRange)) {
            return false;
        }
        VersionRange other = (VersionRange) object;
        if (empty && other.empty) {
            return true;
        }
        if (right == null) {
            return (leftClosed == other.leftClosed) && (other.right == null) && left.equals(other.left);
        }
        return (leftClosed == other.leftClosed) && (rightClosed == other.rightClosed) && left.equals(other.left) && right.equals(other.right);
    }

    /**
     * 使用指定的值返回此版本范围的过滤器字符串
     *
     * @param attributeName {@link String}, 要返回属性的名称.
     * @return {@link String}, 对应属性名称的值.
     * @throws IllegalArgumentException 非有效属性名称时抛出的例外
     *
     */
    public String toFilterString(String attributeName) {
        if (attributeName.length() == 0) {
            throw new IllegalArgumentException("invalid attributeName \"" + attributeName + "\"");
        }
        for (char ch : attributeName.toCharArray()) {
            if ((ch == '=') || (ch == '>') || (ch == '<') || (ch == '~') || (ch == '(') || (ch == ')')) {
                throw new IllegalArgumentException("invalid attributeName \"" + attributeName + "\"");
            }
        }

        StringBuffer result = new StringBuffer(128);
        final boolean needPresence = !leftClosed && ((right == null) || !rightClosed);
        final boolean multipleTerms = needPresence || (right != null);
        if (multipleTerms) {
            result.append("(&");
        }
        if (needPresence) {
            result.append('(');
            result.append(attributeName);
            result.append("=*)");
        }
        if (leftClosed) {
            result.append('(');
            result.append(attributeName);
            result.append(">=");
            result.append(left.toString0());
            result.append(')');
        } else {
            result.append("(!(");
            result.append(attributeName);
            result.append("<=");
            result.append(left.toString0());
            result.append("))");
        }
        if (right != null) {
            if (rightClosed) {
                result.append('(');
                result.append(attributeName);
                result.append("<=");
                result.append(right.toString0());
                result.append(')');
            } else {
                result.append("(!(");
                result.append(attributeName);
                result.append(">=");
                result.append(right.toString0());
                result.append("))");
            }
        }
        if (multipleTerms) {
            result.append(')');
        }

        return result.toString();
    }

    /**
     * 根据{@code range}返回一个版本范围
     *
     * <p>
     * 版本范围的字符串表达形式，可参见 {@link #VersionRange(String)}.
     *
     * @param range {@link String}, 字符串形式的版本范围。 
     * @return {@link VersionRange}.
     * @throws IllegalArgumentException 无效的版本范围字符串格式抛出的例外
     */
    public static VersionRange valueOf(String range) {
        return new VersionRange(range);
    }
}
